package com.bingxue.edu.framework.mybatis.query;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.bingxue.edu.framework.common.model.SortQuery;
import com.bingxue.edu.framework.common.util.CheckUtils;
import com.bingxue.edu.framework.common.util.ReflectUtils;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.ColumnAlias;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.annotation.TableRef;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryTable;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.domain.Sort;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.bingxue.edu.framework.common.constant.StringConstants.ID;
import static com.mybatisflex.core.table.TableInfoFactory.ofEntityClass;

/**
 * 动态查询条件构造类
 * @author panbo
 * @since 2024/7/1
 */
@Slf4j
public class QueryBuilder {

    // query wrapper
    private final QueryWrapper queryWrapper = QueryWrapper.create();
    // 主表
    private QueryTable selfTable;
    // 关联表
    private final Map<String, QueryTable> joinTableMap = new HashMap<>(1);
    // 关联表别名序号
    private int joinTableIndex = 1;

    /**
     * 静态方法构建 QueryWrapper
     *
     * @param query 查询条件
     * @param <Query>   查询条件数据类型
     * @return QueryWrapper
     */
    public static <Query> QueryWrapper build(Query query, SortQuery sortQuery) {
        QueryWrapper queryWrapper = build(query);
        if (sortQuery == null) {
            return queryWrapper;
        }
        for (Sort.Order order : sortQuery.getSort()) {
            if (null == order || StrUtil.isBlank(order.getProperty())) {
                continue;
            }
            queryWrapper.orderBy(CharSequenceUtil.toUnderlineCase(order.getProperty()), order.isAscending());
        }
        return queryWrapper;
    }

    /**
     * 静态方法构建 QueryWrapper
     *
     * @param query 查询条件
     * @param <Query>   查询条件数据类型
     * @return QueryWrapper
     */
    public static <Query> QueryWrapper build(Query query) {
        return new QueryBuilder().buildQueryWrapper(query, true);
    }

    /**
     * 静态方法构建 QueryWrapper
     *
     * @param query 查询条件
     * @param <Query>   查询条件数据类型
     * @return QueryWrapper
     */
    public static <Query> QueryWrapper build(Query query, Boolean headless) {
        return new QueryBuilder().buildQueryWrapper(query, headless);
    }

    /**
     * 构建 QueryWrapper
     *
     * @param query 查询条件
     * @param <Query>   查询条件数据类型
     * @return QueryWrapper
     */
    private <Query> QueryWrapper buildQueryWrapper(Query query, Boolean headless) {
        // 没有查询条件，直接返回
        if (query == null) {
            return queryWrapper;
        }
//        selfTable = resolveQueryTable(query.getClass()).as("t");

        selfTable = resolveQueryTable(query.getClass());
        if(!headless) {
            queryWrapper.from(selfTable);
        }
        // 获取查询条件中所有的字段
        List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
        Map<Field, Object> fieldValueMap = resolveFieldValueMap(query, fieldList);

        for (Field field : fieldValueMap.keySet()) {
            Object fieldValue = fieldValueMap.get(field);
            // 优先处理QueryCondition.List注解
            com.bingxue.edu.framework.mybatis.query.Query.List queryConditionList = field.getAnnotation(com.bingxue.edu.framework.mybatis.query.Query.List.class);
            if (queryConditionList != null) {
                List<com.bingxue.edu.framework.mybatis.query.Query> queryConditions = Arrays.stream(queryConditionList.value()).toList();
                com.mybatisflex.core.query.QueryCondition condition = com.mybatisflex.core.query.QueryCondition.createEmpty();
                for (com.bingxue.edu.framework.mybatis.query.Query queryCondition : queryConditions) {
                    QueryTable queryTable = resolveQueryTable(queryCondition);
                    String columnName = resolveColumnName(field, queryCondition);
                    QueryType queryType = queryCondition != null ? queryCondition.type() : QueryType.EQ;
                    condition = condition.or(resolveQueryCondition(queryTable, columnName, fieldValue, queryType));
                }
                queryWrapper.and(condition);
            } else {
                // 当QueryCondition.List不存在时，处理QueryCondition注解。
                // 注意QueryCondition为空时的处理逻辑一致，所以不单独判断queryCondition为空的情况。
                com.bingxue.edu.framework.mybatis.query.Query queryCondition = field.getAnnotation(com.bingxue.edu.framework.mybatis.query.Query.class);
                if (queryCondition != null && queryCondition.ignored()) {
                    continue;
                }
                QueryTable queryTable = resolveQueryTable(queryCondition);
                String columnName = resolveColumnName(field, queryCondition);
                QueryType queryType = queryCondition != null ? queryCondition.type() : QueryType.EQ;
                queryWrapper.and(resolveQueryCondition(queryTable, columnName, fieldValue, queryType));
            }
        }
        return queryWrapper;
    }

    /**
     * 解析查询条件
     *
     * @param queryTable 查询表信息
     * @param columnName 列名
     * @param fieldValue 字段值
     * @param queryType 查询方式
     * @return 查询条件
     */
    private com.mybatisflex.core.query.QueryCondition resolveQueryCondition(QueryTable queryTable, String columnName, Object fieldValue, QueryType queryType) {
        QueryColumn column = new QueryColumn(queryTable, columnName);
        return switch (queryType) {
            case EQ -> column.eq(fieldValue);
            case NE -> column.ne(fieldValue);
            case GT -> column.gt(fieldValue);
            case GE -> column.ge(fieldValue);
            case LT -> {
                if (fieldValue instanceof LocalDate) {
                    LocalDate localDate = ((LocalDate) fieldValue).plusDays(1);
                    yield column.lt(localDate);
                }
                yield column.lt(fieldValue);
            }
            case LE -> {
                if (fieldValue instanceof LocalDate) {
                    LocalDate localDate = ((LocalDate) fieldValue).plusDays(1);
                    yield column.lt(localDate);
                }
                yield column.le(fieldValue);
            }
            case BETWEEN -> {
                @SuppressWarnings("unchecked")
                List<Object> between = (List<Object>) fieldValue;
                ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
                yield column.between(between.get(0), between.get(1));
            }
            case LIKE -> column.like(fieldValue);
            case LIKE_LEFT -> column.likeLeft(fieldValue);
            case LIKE_RIGHT -> column.likeRight(fieldValue);
            case IN -> {
                if (fieldValue instanceof Collection) {
                    yield column.in((Collection<?>) fieldValue);
                } else if (fieldValue.getClass().isArray()) {
                    yield column.in((Object[]) fieldValue);
                } else {
                    yield column.in(fieldValue);
                }
            }
            case NOT_IN -> {
                if (fieldValue instanceof Collection) {
                    yield column.notIn((Collection<?>) fieldValue);
                } else if (fieldValue.getClass().isArray()) {
                    yield column.notIn((Object[]) fieldValue);
                } else {
                    yield column.ne(fieldValue);
                }
            }
            case IS_NULL -> column.isNull();
            case IS_NOT_NULL -> column.isNotNull();
        };
    }

    /**
     * 从类中解析查询表
     * @param entityClass 类
     * @return QueryTable
     */
    private QueryTable resolveQueryTable(Class<?> entityClass) {
        Table table = entityClass.getAnnotation(Table.class);
        if (table != null) {
            return new QueryTable(table.schema(), table.value());
        }
        TableRef tableRef = entityClass.getAnnotation(TableRef.class);
        if (tableRef != null ) {
            TableInfo refTableInfo = ofEntityClass(tableRef.value());
            return new QueryTable(refTableInfo.getSchema(), refTableInfo.getTableName());
        }
        String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
        return new QueryTable(tableName);
    }

    /**
     * 从查询类中解析出字段-值map映射
     * @param query 查询类
     * @param queryFields 查询字段
     * @return 字段-值map
     * @param <Query> 查询类类型
     */
    private <Query> Map<Field, Object> resolveFieldValueMap(Query query, List<Field> queryFields) {
        HashMap<Field, Object> fieldValueMap = new HashMap<>();
        for (Field field : queryFields) {
            boolean accessible = field.canAccess(query);
            try {
                field.setAccessible(true);
                Object fieldValue = field.get(query);
                // 如果字段值为空，跳过
                if (ObjectUtil.isNotEmpty(fieldValue)) {
                    fieldValueMap.put(field, fieldValue);
                }
            } catch (IllegalAccessException e) {
                log.error("通过反射获取属性值出错：{}", e.getMessage());
            } finally {
                field.setAccessible(accessible);
            }
        }
        return fieldValueMap;
    }

    /**
     * 解析列名称
     * @param field 字段
     * @param query 查询注解
     * @return 列名称
     */
    private String resolveColumnName(Field field, Query query) {
        String columnName = null;
        if (query != null && !Strings.isEmpty(query.field())) {
            columnName = StrUtil.toUnderlineCase(query.field());
        } else if (field.isAnnotationPresent(ColumnAlias.class)) {
            columnName = field.getAnnotation(ColumnAlias.class).value()[0];
        } else if (field.isAnnotationPresent(Column.class)) {
            columnName = field.getAnnotation(Column.class).value();
        }
        if (Strings.isEmpty(columnName)) {
            columnName = StrUtil.toUnderlineCase(field.getName());
        }
        return columnName;
    }

    /**
     * 从查询注解中解析查询表
     * @param query 查询注解
     * @return QueryTable
     */
    private QueryTable resolveQueryTable(Query query) {
        if (!isJoinQuery(query)) {
            return selfTable;
        }
        Join[] joins = query.join();
        Join joinCondition = joins[0];
        Class<?> target = joinCondition.target();
        TableInfo joinTableInfo;
        if (!target.equals(Void.class)) {
            joinTableInfo = ofEntityClass(target);
        } else {
            String targetTable = joinCondition.targetTable();
            CheckUtils.throwIfBlank(targetTable, "关联表不能为空");
            joinTableInfo = TableInfoFactory.ofTableName(targetTable);
        }
        String joinTableName = joinTableInfo.getTableName();
        String selfFieldName = StrUtil.isBlank(joinCondition.selfField()) ? ID : StrUtil.toUnderlineCase(joinCondition.selfField());
        String targetFieldName = Strings.isEmpty(joinCondition.targetField()) ? joinTableInfo.getKeyColumns() : StrUtil.toUnderlineCase(joinCondition.targetField());
        String joinKey = String.format("%s:%s:%s", joinTableName, selfFieldName, targetFieldName);
        return joinTableMap.computeIfAbsent(joinKey, key -> {
            String alias = "t" + joinTableIndex;
            joinTableIndex += 1;
            QueryTable joinTable = new QueryTable(joinTableInfo.getSchema(), joinTableInfo.getTableName()).as(alias);
            queryWrapper.leftJoin(joinTable).on(new QueryColumn(selfTable, selfFieldName).eq(new QueryColumn(joinTable, targetFieldName)));
            return joinTable;
        });
    }

    /**
     * 判断是否为关联查询注解
     *
     * @param query 查询注解
     * @return 是否为关联查询注解
     */
    private static boolean isJoinQuery(Query query) {
        if (query == null) {
            return false;
        }
        Join[] joins = query.join();
        return joins != null && joins.length > 0;
    }
}

